﻿////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// <copyright>Copyright 2008-2011 Andreas Huber Doenni</copyright>
// Distributed under the Microsoft Public License (Ms-PL).
// See accompanying file License.txt or copy at http://mews.codeplex.com/license.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

namespace Mews.Net.Nntp.Cache
{
    using System;
    using System.Collections.Generic;
    using System.Data;
    using System.Data.SqlServerCe;

    /// <summary>Provides helper methods for <see cref="SqlCeResultSet"/>.</summary>
    internal static class SqlCeHelper
    {
        internal static bool SeekAndRead(this SqlCeDataReader reader, DbSeekOptions seekOptions, params object[] key)
        {
            return reader.Seek(seekOptions, key) && reader.Read();
        }

        internal static T GetNullableColumn<T>(this SqlCeDataReader reader, string columnName)
        {
            object value = reader[columnName];
            return value == DBNull.Value ? default(T) : (T)value;
        }

        internal static void SetValue(this SqlCeResultSet resultSet, string columnName, object value)
        {
            resultSet.SetValue(resultSet.GetOrdinal(columnName), value);
        }

        /// <summary>Inserts a single row with the elements of <paramref name="values"/> assigned to the
        /// corresponding columns with the names identified with <paramref name="columnNames"/>.</summary>
        /// <exception cref="ArgumentException">An element of <paramref name="values"/> is a string and exceeds the
        /// maximum allowed length defined for the corresponding column, or a unique constraint was violated.
        /// </exception>
        /// <exception cref="ArgumentNullException">An element of <paramref name="values"/> is null and the
        /// corresponding column does not allow NULL values.</exception>
        /// <remarks>After the insert operation, the result set is positioned on the inserted row.</remarks>
        internal static void Insert(this SqlCeResultSet resultSet, string[] columnNames, params object[] values)
        {
            var row = resultSet.CreateRecord();

            // Ensure that we get an exception if the lengths are not equal
            int pastEnd = Math.Max(columnNames.Length, values.Length);

            try
            {
                for (int index = 0; index < pastEnd; ++index)
                {
                    row[columnNames[index]] = values[index];
                }

                resultSet.Insert(row, DbInsertOptions.PositionOnInsertedRow);
            }
            catch (InvalidOperationException ex)
            {
                throw new ArgumentException(ex.Message, ex);
            }
            catch (SqlCeException ex)
            {
                switch (ex.NativeError)
                {
                    case 25005:
                        throw new ArgumentNullException(null, ex.Message);
                    case 25016:
                        throw new ArgumentException(
                            "A constraint was violated, see InnerException for more information.", ex);
                    case 25026:
                        throw new InvalidOperationException(
                            "A child object cannot be added to a deleted parent object.", ex);
                    default:
                        throw;
                }
            }
        }

        /// <summary>Updates the first row with an index key matching <paramref name="key"/>.</summary>
        /// <exception cref="InvalidOperationException">No row exists with a key equal to <paramref name="key"/>.
        /// </exception>
        internal static void Update(
            this SqlCeResultSet resultSet, object[] key, string[] columnNames, params object[] values)
        {
            if (!resultSet.SeekAndRead(DbSeekOptions.FirstEqual, key))
            {
                throw new InvalidOperationException("The object to be updated has been deleted.");
            }

            // Ensure that we get an exception if the lengths are not equal
            int pastEnd = Math.Max(columnNames.Length, values.Length);

            for (int index = 0; index < pastEnd; ++index)
            {
                resultSet.SetValue(resultSet.GetOrdinal(columnNames[index]), values[index]);
            }

            try
            {
                resultSet.Update();
            }
            catch (SqlCeException ex)
            {
                if (ex.NativeError == 25005)
                {
                    throw new ArgumentNullException(null, ex.Message);
                }
                else
                {
                    throw;
                }
            }
            catch (InvalidOperationException ex)
            {
                throw new ArgumentException(ex.Message, ex);
            }
        }

        /// <summary>Deletes the first row with an index key matching <paramref name="key"/>.</summary>
        /// <exception cref="InvalidOperationException">No row exists with a key equal to <paramref name="key"/>.
        /// </exception>
        internal static void Delete(this SqlCeResultSet resultSet, params object[] key)
        {
            if (!resultSet.SeekAndRead(DbSeekOptions.FirstEqual, key))
            {
                throw new InvalidOperationException("The object to be deleted has already been deleted.");
            }

            resultSet.Delete();
        }

        internal static IEnumerable<TRow> Select<TRow>(
            Func<SqlCeDataReader, TRow> createCallback, SqlCeConnection connection, string tableName)
        {
            return Select(createCallback, CommandHelper.Create(connection, tableName));
        }

        internal static IEnumerable<TRow> Select<TRow>(
            Func<SqlCeDataReader, TRow> createCallback,
            SqlCeConnection connection,
            string tableName,
            string indexName,
            DbRangeOptions rangeOptions,
            object[] startData,
            object[] endData)
        {
            return Select(
                createCallback,
                CommandHelper.Create(connection, tableName, indexName, rangeOptions, startData, endData));
        }

        internal static T Insert<T>(
            Func<SqlCeDataReader, T> createCallback,
            SqlCeConnection connection,
            string tableName,
            string[] columnNames,
            params object[] values)
        {
            using (var command = CommandHelper.Create(connection, tableName))
            using (var resultSet = command.ExecuteResultSet(ResultSetOptions.Updatable))
            {
                resultSet.Insert(columnNames, values);
                return createCallback(resultSet);
            }
        }

        internal static void Delete(SqlCeConnection connection, string tableName, params object[] primaryKey)
        {
            using (var command = CommandHelper.Create(connection, tableName))
            using (var resultSet = command.ExecuteResultSet(ResultSetOptions.Updatable))
            {
                resultSet.Delete(primaryKey);
            }
        }

        internal static int GetColumnSize(DataTable schemaTable, string columnName)
        {
            return (int)schemaTable.Select("ColumnName='" + columnName + "'")[0]["ColumnSize"];
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        private static IEnumerable<TRow> Select<TRow>(Func<SqlCeDataReader, TRow> createCallback, SqlCeCommand command)
        {
            using (command)
            using (var reader = command.ExecuteReader())
            {
                while (reader.Read())
                {
                    yield return createCallback(reader);
                }
            }
        }
    }
}
